<template>
  <Renderer ref="renderer" resize orbit-ctrl pointer>
    <Camera :position="{ z: 200 }" />
    <Scene>
      <AmbientLight color="#808080" />
      <PointLight color="#ff6000" />
      <PointLight ref="light" color="#0060ff" :intensity="0.5" />
      <PointLight color="#ff6000" :intensity="0.5" :position="{ x: 100 }" />
      <PointLight color="#0000ff" :intensity="0.5" :position="{ x: -100 }" />

      <InstancedMesh ref="imesh" :count="NUM_INSTANCES">
        <BoxGeometry :width="2" :height="2" :depth="10" />
        <StandardMaterial :props="{ transparent: true, opacity: 0.9, metalness: 0.8, roughness: 0.5 }" />
      </InstancedMesh>

      <Text text="TroisJS" font-src="/fonts/helvetiker_regular.typeface.json" align="center" :size="30" :height="5"
        :position="{ x: 0, y: 0, z: 0 }" :cast-shadow="true">
        <PhongMaterial />
      </Text>
    </Scene>
    <EffectComposer>
      <RenderPass />
      <UnrealBloomPass :strength="1" />
      <HalftonePass :radius="1" :scatter="0" />
    </EffectComposer>
  </Renderer>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import * as THREE from 'three'
import {
  AmbientLight,
  BoxGeometry,
  Camera,
  EffectComposer,
  HalftonePass,
  InstancedMesh,
  PhongMaterial,
  PointLight,
  Renderer,
  RenderPass,
  StandardMaterial,
  Scene,
  Text,
  UnrealBloomPass
} from 'troisjs'
const { randFloat: rnd, randFloatSpread: rndFS } = THREE.MathUtils

let light: THREE.PointLight
let renderer: any
let imesh: THREE.InstancedMesh

export default defineComponent({
  name: 'trois-exam',
  components: {
    AmbientLight,
    BoxGeometry,
    Camera,
    EffectComposer,
    HalftonePass,
    InstancedMesh,
    PhongMaterial,
    PointLight,
    Renderer,
    RenderPass,
    StandardMaterial,
    Scene,
    Text,
    UnrealBloomPass
  },
  setup () {
    const NUM_INSTANCES = 2000
    const instances = []
    const target = new THREE.Vector3()
    const dummyO = new THREE.Object3D()
    const dummyV = new THREE.Vector3()
    for (let i = 0; i < NUM_INSTANCES; i++) {
      instances.push({
        scaleZ: rnd(0.1, 1),
        scale: rnd(0.2, 1),
        velocity: new THREE.Vector3(rndFS(2), rndFS(2), rndFS(2)),
        attraction: 0.03 + rnd(-0.01, 0.01),
        vlimit: 1.2 + rnd(-0.1, 0.1),
        position: new THREE.Vector3(rndFS(200), rndFS(200), rndFS(200))
      })
    }
    return {
      instances,
      target,
      dummyO,
      dummyV,
      NUM_INSTANCES
    }
  },
  mounted () {
    renderer = this.$refs.renderer as any
    light = (this.$refs.light as any).light as THREE.PointLight
    imesh = (this.$refs.imesh as any).mesh as THREE.InstancedMesh
    if (renderer && imesh && light) this.init()
  },
  methods: {
    init () {
      // init instanced mesh matrix
      for (let i = 0; i < this.NUM_INSTANCES; i++) {
        const { position, scale, scaleZ } = this.instances[i]
        // 矩阵变换（Matrix transformations）
        this.dummyO.position.copy(position)
        this.dummyO.scale.set(scale, scale, scaleZ)
        // https://threejs.org/docs/index.html#manual/zh/introduction/Matrix-transformations
        this.dummyO.updateMatrix()
        imesh.setMatrixAt(i, this.dummyO.matrix)
      }
      imesh.instanceMatrix.needsUpdate = true
      // animate
      renderer.onBeforeRender(this.animate)
    },
    animate () {
      const { pointer } = renderer.three
      this.target.copy(pointer.positionV3)
      light.position.copy(this.target)
      for (let i = 0; i < this.NUM_INSTANCES; i++) {
        const { position, scale, scaleZ, velocity, attraction, vlimit } = this.instances[i]
        this.dummyV.copy(this.target).sub(position).normalize().multiplyScalar(attraction)
        velocity.add(this.dummyV).clampScalar(-vlimit, vlimit)
        position.add(velocity)
        this.dummyO.position.copy(position)
        this.dummyO.scale.set(scale, scale, scaleZ)
        this.dummyO.lookAt(this.dummyV.copy(position).add(velocity))
        this.dummyO.updateMatrix()
        imesh.setMatrixAt(i, this.dummyO.matrix)
      }
      imesh.instanceMatrix.needsUpdate = true
    }
  }
})
</script>
